/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.contract.spec

import org.springframework.cloud.contract.spec.internal.*

/**
 * @author Tim Ysewyn
 * @since 2.2.0
 */
@ContractDslMarker
class ContractDsl {

    companion object {
        fun contract(dsl: ContractDsl.() -> Unit): Contract = ContractDsl().apply(dsl).get()
    }

    /**
     * You can set the level of priority of this contract. If there are two contracts
     * mapped for example to the same endpoint, then the one with greater priority should
     * take precedence. A priority of 1 is highest and takes precedence over a priority of
     * 2.
     */
    var priority: Int? = null

    /**
     * The label by which you'll reference the contract on the message consumer side.
     */
    var label: String? = null

    /**
     * Description of a contract. May be used in the documentation generation.
     */
    var description: String? = null

    /**
     * Name of the generated test / stub. If not provided then the file name will be used.
     * If you have multiple contracts in a single file and you don't provide this value
     * then a prefix will be added to the file with the index number while iterating over
     * the collection of contracts.
     *
     * Remember to have a unique name for every single contract. Otherwise you might
     * generate tests that have two identical methods or you will override the stubs.
     */
    var name: String? = null

    /**
     * Whether the contract should be ignored or not.
     */
    var ignored: Boolean = false

    /**
     * Whether the contract is in progress. It's not ignored, but the feature is not yet
     * finished. Used together with the {@code generateStubs} option.
     */
    var inProgress: Boolean = false

    /**
     * The HTTP request part of the contract.
     */
    var request: Request? = null

    /**
     * The HTTP response part of the contract.
     */
    var response: Response? = null

    /**
     * The input side of a messaging contract.
     */
    var input: Input? = null

    /**
     * The output side of a messaging contract.
     */
    var outputMessage: OutputMessage? = null

    /**
     * The HTTP request part of the contract.
     * @param configurer lambda to configure the HTTP request
     */
    fun request(configurer: RequestDsl.() -> Unit) {
        request = RequestDsl().apply(configurer).get()
    }

    /**
     * The HTTP response part of the contract.
     * @param configurer lambda to configure the HTTP response
     */
    fun response(configurer: ResponseDsl.() -> Unit) {
        response = ResponseDsl().apply(configurer).get()
    }

    /**
     * The input part of the contract.
     * @param configurer lambda to configure the input message
     */
    fun input(configurer: InputDsl.() -> Unit) {
        input = InputDsl().apply(configurer).get()
    }

    /**
     * The output part of the contract.
     * @param configurer lambda to configure the output message
     */
    fun outputMessage(configurer: OutputMessageDsl.() -> Unit) {
        outputMessage = OutputMessageDsl().apply(configurer).get()
    }

    private fun get(): Contract {
        val contract = Contract()
        priority?.also { contract.priority = priority }
        label?.also { contract.label = label }
        description?.also { contract.description = description }
        name?.also { contract.name = name }
        contract.ignored = ignored
        contract.inProgress = inProgress
        request?.also { contract.request = request }
        response?.also { contract.response = response }
        input?.also { contract.input = input }
        outputMessage?.also { contract.outputMessage = outputMessage }
        return contract
    }

}
